/*
REWRITTEN BY XINEF
*/

#include "ScriptMgr.h"
#include "InstanceScript.h"
#include "karazhan.h"

#define MAX_ENCOUNTER      12

class instance_karazhan : public InstanceMapScript
{
	public:
		instance_karazhan() : InstanceMapScript("instance_karazhan", 532) { }

		InstanceScript* GetInstanceScript(InstanceMap* map) const
		{
			return new instance_karazhan_InstanceMapScript(map);
		}

		struct instance_karazhan_InstanceMapScript : public InstanceScript
		{
			instance_karazhan_InstanceMapScript(Map* map) : InstanceScript(map) { }

			std::string strSaveData;

			uint32 m_uiOperaEvent;
			uint32 m_uiOzDeathCount;
			uint32 m_auiEncounter[MAX_ENCOUNTER];

			uint64 m_uiCurtainGUID;
			uint64 m_uiStageDoorLeftGUID;
			uint64 m_uiStageDoorRightGUID;
			uint64 m_uiKilrekGUID;
			uint64 m_uiTerestianGUID;
			uint64 m_uiMoroesGUID;
			uint64 m_uiNightBaneGUID;
			uint64 m_uiLibraryDoor;                                     // Door at Shade of Aran
			uint64 m_uiMassiveDoor;                                     // Door at Netherspite
			uint64 m_uiSideEntranceDoor;                                // Side Entrance
			uint64 m_uiGamesmansDoor;                                   // Door before Chess
			uint64 m_uiGamesmansExitDoor;                               // Door after Chess
			uint64 m_uiNetherspaceDoor;                                // Door at Malchezaar
			uint64 m_uiServantsAccessDoor;                              // Door to Brocken Stair
			uint64 MastersTerraceDoor[2];
			uint64 ImageGUID;
			uint64 m_uiShadeOfAranGUID;

			void Initialize()
			{
				memset(&m_auiEncounter, 0, sizeof(m_auiEncounter));

				// 1 - OZ, 2 - HOOD, 3 - RAJ, this never gets altered.
				m_uiOperaEvent = urand(1, 3);
				m_uiOzDeathCount = 0;

				m_uiCurtainGUID = 0;
				m_uiStageDoorLeftGUID = 0;
				m_uiStageDoorRightGUID = 0;

				m_uiKilrekGUID = 0;
				m_uiTerestianGUID = 0;
				m_uiMoroesGUID = 0;
				m_uiNightBaneGUID = 0;
				m_uiShadeOfAranGUID = 0;

				m_uiLibraryDoor = 0;
				m_uiMassiveDoor = 0;
				m_uiSideEntranceDoor = 0;
				m_uiGamesmansDoor = 0;
				m_uiGamesmansExitDoor = 0;
				m_uiNetherspaceDoor = 0;
				m_uiServantsAccessDoor = 0;
				MastersTerraceDoor[0] = 0;
				MastersTerraceDoor[1] = 0;
				ImageGUID = 0;
			}

			bool IsEncounterInProgress() const
			{
				for (uint8 i = 0; i < MAX_ENCOUNTER; ++i)
				if (m_auiEncounter[i] == IN_PROGRESS)
					return true;

				return false;
			}

			void OnCreatureCreate(Creature* creature)
			{
				switch (creature->GetEntry())
				{
				case 17229:   m_uiKilrekGUID = creature->GetGUID();      break;
				case 15688:   m_uiTerestianGUID = creature->GetGUID();   break;
				case 15687:   m_uiMoroesGUID = creature->GetGUID();      break;
				case 16524:   m_uiShadeOfAranGUID = creature->GetGUID(); break;
				}
			}

			void SetData(uint32 type, uint32 uiData) override
			{
				switch (type)
				{
				case TYPE_ATTUMEN:              m_auiEncounter[0] = uiData; break;
				case TYPE_MOROES:
					if (m_auiEncounter[1] == DONE)
						break;
					m_auiEncounter[1] = uiData;
					break;
				case TYPE_MAIDEN:               m_auiEncounter[2] = uiData; break;
				case TYPE_OPTIONAL_BOSS:        m_auiEncounter[3] = uiData; break;
				case TYPE_OPERA:
					m_auiEncounter[4] = uiData;
					if (uiData == DONE)
						instance->UpdateEncounterState(ENCOUNTER_CREDIT_KILL_CREATURE, 16812, NULL);
					break;
				case TYPE_CURATOR:              m_auiEncounter[5] = uiData; break;
				case TYPE_ARAN:                 m_auiEncounter[6] = uiData; break;
				case TYPE_TERESTIAN:            m_auiEncounter[7] = uiData; break;
				case TYPE_NETHERSPITE:          m_auiEncounter[8] = uiData; break;
				case TYPE_CHESS:
					if (uiData == DONE)
						DoRespawnGameObject(m_uiServantsAccessDoor, DAY);
					m_auiEncounter[9] = uiData;
					break;
				case TYPE_MALCHEZZAR:           m_auiEncounter[10] = uiData; break;
				case TYPE_NIGHTBANE:
					if (m_auiEncounter[11] != DONE)
						m_auiEncounter[11] = uiData;
					break;
				case DATA_OPERA_OZ_DEATHCOUNT:
					if (uiData == SPECIAL)
						++m_uiOzDeathCount;
					else if (uiData == IN_PROGRESS)
						m_uiOzDeathCount = 0;
					break;
				}

				if (uiData == DONE)
				{
					OUT_SAVE_INST_DATA;

					std::ostringstream saveStream;
					saveStream << m_auiEncounter[0] << ' ' << m_auiEncounter[1] << ' ' << m_auiEncounter[2] << ' '
						<< m_auiEncounter[3] << ' ' << m_auiEncounter[4] << ' ' << m_auiEncounter[5] << ' ' << m_auiEncounter[6] << ' '
						<< m_auiEncounter[7] << ' ' << m_auiEncounter[8] << ' ' << m_auiEncounter[9] << ' ' << m_auiEncounter[10] << ' ' << m_auiEncounter[11];

					strSaveData = saveStream.str();

					SaveToDB();
					OUT_SAVE_INST_DATA_COMPLETE;
				}
			}

			void SetData64(uint32 identifier, uint64 data)
			{
				switch (identifier)
				{
				case DATA_IMAGE_OF_MEDIVH: ImageGUID = data;
				case DATA_NIGHTBANE:       m_uiNightBaneGUID = data;
				}
			}

			void OnGameObjectCreate(GameObject* pGo)
			{
				switch (pGo->GetEntry())
				{
				case 183932:   m_uiCurtainGUID = pGo->GetGUID();         break;
				case 184278:
					m_uiStageDoorLeftGUID = pGo->GetGUID();
					if (m_auiEncounter[4] == DONE)
						pGo->SetGoState(GO_STATE_ACTIVE);
					break;
				case 184279:
					m_uiStageDoorRightGUID = pGo->GetGUID();
					if (m_auiEncounter[4] == DONE)
						pGo->SetGoState(GO_STATE_ACTIVE);
					break;
				case 184517:   m_uiLibraryDoor = pGo->GetGUID();         break;
				case 185521:   m_uiMassiveDoor = pGo->GetGUID();         break;
				case 184276:   m_uiGamesmansDoor = pGo->GetGUID();         break;
				case 184277:   m_uiGamesmansExitDoor = pGo->GetGUID();         break;
				case 185134:   m_uiNetherspaceDoor = pGo->GetGUID();         break;
				case 184274:   MastersTerraceDoor[0] = pGo->GetGUID();            break;
				case 184280:   MastersTerraceDoor[1] = pGo->GetGUID();            break;
				case 184275:
					m_uiSideEntranceDoor = pGo->GetGUID();
					if (m_auiEncounter[4] == DONE)
						pGo->SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_LOCKED);
					else
						pGo->RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_LOCKED);
					break;
				case 184281:   m_uiServantsAccessDoor = pGo->GetGUID();         break;
				}

				switch (m_uiOperaEvent)
				{
					//TODO: Set Object visibilities for Opera based on performance
				case EVENT_OZ:
					break;

				case EVENT_HOOD:
					break;

				case EVENT_RAJ:
					break;
				}
			}

			std::string GetSaveData()
			{
				OUT_SAVE_INST_DATA;
				std::ostringstream stream;
				stream << "K Z " << " "
					<< m_auiEncounter[0] << " " << m_auiEncounter[1] << " "
					<< m_auiEncounter[2] << " " << m_auiEncounter[3] << " "
					<< m_auiEncounter[4] << " " << m_auiEncounter[5] << " "
					<< m_auiEncounter[6] << " " << m_auiEncounter[7] << " "
					<< m_auiEncounter[8] << " " << m_auiEncounter[9] << " "
					<< m_auiEncounter[10] << " " << m_auiEncounter[11];
				char* out = new char[stream.str().length() + 1];
				strcpy(out, stream.str().c_str());
				if (out)
				{
					OUT_SAVE_INST_DATA_COMPLETE;
					return out;
				}

				return NULL;
			}

			uint32 GetData(uint32 uiData) const
			{
				switch (uiData)
				{
				case TYPE_ATTUMEN:              return m_auiEncounter[0];
				case TYPE_MOROES:               return m_auiEncounter[1];
				case TYPE_MAIDEN:               return m_auiEncounter[2];
				case TYPE_OPTIONAL_BOSS:        return m_auiEncounter[3];
				case TYPE_OPERA:                return m_auiEncounter[4];
				case TYPE_CURATOR:              return m_auiEncounter[5];
				case TYPE_ARAN:                 return m_auiEncounter[6];
				case TYPE_TERESTIAN:            return m_auiEncounter[7];
				case TYPE_NETHERSPITE:          return m_auiEncounter[8];
				case TYPE_CHESS:                return m_auiEncounter[9];
				case TYPE_MALCHEZZAR:           return m_auiEncounter[10];
				case TYPE_NIGHTBANE:            return m_auiEncounter[11];
				case DATA_OPERA_PERFORMANCE:    return m_uiOperaEvent;
				case DATA_OPERA_OZ_DEATHCOUNT:  return m_uiOzDeathCount;
				case DATA_IMAGE_OF_MEDIVH:      return ImageGUID;
				}

				return 0;
			}

			uint64 GetData64(uint32 uiData) const
			{
				switch (uiData)
				{
				case DATA_KILREK:                   return m_uiKilrekGUID;
				case DATA_TERESTIAN:                return m_uiTerestianGUID;
				case DATA_MOROES:                   return m_uiMoroesGUID;
				case DATA_GO_STAGEDOORLEFT:         return m_uiStageDoorLeftGUID;
				case DATA_GO_STAGEDOORRIGHT:        return m_uiStageDoorRightGUID;
				case DATA_GO_CURTAINS:              return m_uiCurtainGUID;
				case DATA_GO_LIBRARY_DOOR:          return m_uiLibraryDoor;
				case DATA_GO_MASSIVE_DOOR:          return m_uiMassiveDoor;
				case DATA_GO_SIDE_ENTRANCE_DOOR:    return m_uiSideEntranceDoor;
				case DATA_GO_GAME_DOOR:             return m_uiGamesmansDoor;
				case DATA_GO_GAME_EXIT_DOOR:        return m_uiGamesmansExitDoor;
				case DATA_GO_NETHER_DOOR:           return m_uiNetherspaceDoor;
				case DATA_IMAGE_OF_MEDIVH:          return ImageGUID;
				case DATA_MASTERS_TERRACE_DOOR_1:   return MastersTerraceDoor[0];
				case DATA_MASTERS_TERRACE_DOOR_2:   return MastersTerraceDoor[1];
				case DATA_NIGHTBANE:                return m_uiNightBaneGUID;
				case DATA_SHADE_OF_ARAN:            return m_uiShadeOfAranGUID;
				}

				return 0;
			}

			void Load(const char* in)
			{
				if (!in)
				{
					OUT_LOAD_INST_DATA_FAIL;
					return;
				}

				OUT_LOAD_INST_DATA(in);
				std::istringstream stream(in);
				char dataHead1, dataHead2;
				stream >> dataHead1 >> dataHead2 >>
					m_auiEncounter[0] >> m_auiEncounter[1] >>
					m_auiEncounter[2] >> m_auiEncounter[3] >>
					m_auiEncounter[4] >> m_auiEncounter[5] >>
					m_auiEncounter[6] >> m_auiEncounter[7] >>
					m_auiEncounter[8] >> m_auiEncounter[9] >>
					m_auiEncounter[10] >> m_auiEncounter[11];
				if (dataHead1 != 'K' || dataHead2 != 'Z')
				{
					sLog->outError("SD2: Karazhan corrupted save uiData.");
					for (uint8 i = 0; i < MAX_ENCOUNTER; ++i)
					if (m_auiEncounter[i] == IN_PROGRESS)                // Do not load an encounter as "In Progress" - reset it instead.
						m_auiEncounter[i] = NOT_STARTED;
				}
				else OUT_LOAD_INST_DATA_COMPLETE;
			}
		};
};

class spell_karazhan_brittle_bones : public SpellScriptLoader
{
	public:
		spell_karazhan_brittle_bones() : SpellScriptLoader("spell_karazhan_brittle_bones") { }

		class spell_karazhan_brittle_bones_AuraScript : public AuraScript
		{
			PrepareAuraScript(spell_karazhan_brittle_bones_AuraScript);

			void CalcPeriodic(AuraEffect const* /*effect*/, bool& isPeriodic, int32& amplitude)
			{
				isPeriodic = true;
				amplitude = 5000;
			}

			void Update(AuraEffect const* effect)
			{                
				PreventDefaultAction();
				if (roll_chance_i(35))
					GetUnitOwner()->CastSpell(GetUnitOwner(), SPELL_RATTLED, true);
			}

			void Register()
			{
				DoEffectCalcPeriodic += AuraEffectCalcPeriodicFn(spell_karazhan_brittle_bones_AuraScript::CalcPeriodic, EFFECT_0, SPELL_AURA_DUMMY);
				OnEffectPeriodic += AuraEffectPeriodicFn(spell_karazhan_brittle_bones_AuraScript::Update, EFFECT_0, SPELL_AURA_DUMMY);
			}
		};

		AuraScript* GetAuraScript() const
		{
			return new spell_karazhan_brittle_bones_AuraScript();
		}
};

class spell_karazhan_overload : public SpellScriptLoader
{
	public:
		spell_karazhan_overload() : SpellScriptLoader("spell_karazhan_overload") { }

		class spell_karazhan_overload_AuraScript : public AuraScript
		{
			PrepareAuraScript(spell_karazhan_overload_AuraScript);

			void PeriodicTick(AuraEffect const* auraEffect)
			{                
				PreventDefaultAction();
				GetUnitOwner()->CastCustomSpell(SPELL_OVERLOAD, SPELLVALUE_BASE_POINT0, int32(auraEffect->GetAmount() * (2.0, auraEffect->GetTickNumber())), GetUnitOwner(), true);
			}

			void Register()
			{
				OnEffectPeriodic += AuraEffectPeriodicFn(spell_karazhan_overload_AuraScript::PeriodicTick, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL);
			}
		};

		AuraScript* GetAuraScript() const
		{
			return new spell_karazhan_overload_AuraScript();
		}
};

class spell_karazhan_blink : public SpellScriptLoader
{
    public:
        spell_karazhan_blink() : SpellScriptLoader("spell_karazhan_blink") { }

        class spell_karazhan_blink_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_karazhan_blink_SpellScript);

            void HandleDummy(SpellEffIndex effIndex)
            {
				PreventHitDefaultEffect(effIndex);
				GetCaster()->getThreatManager().resetAllAggro();
				if (Unit* target = GetHitUnit())
					GetCaster()->CastSpell(target, SPELL_BLINK, true);
            }

            void Register()
            {
                OnEffectHitTarget += SpellEffectFn(spell_karazhan_blink_SpellScript::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_karazhan_blink_SpellScript();
        }
};

void AddSC_instance_karazhan()
{
    new instance_karazhan();
	new spell_karazhan_brittle_bones();
	new spell_karazhan_overload();
	new spell_karazhan_blink();
}
